﻿using System;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Reflection;

namespace LookupField.Services
{
    public class LookupService : ILookupService
    {
       public JsonResult Search(string type, string query, ObjectContext ctx){

            try
            {
                var modelType = Type.GetType(type);

                var queryObject = CreateQuery(modelType, query, ctx);

                var records = InvokeToList(modelType, queryObject);
                
                return Json(new { Result = "OK", Records = records, IdColumn =  "Id"});
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }

        /// <summary>
        /// Cria o método CreateQuery<T> utilizando o contexto passado e o tipo do modelo
        /// </summary>
        /// <param name="modelType"></param>
        /// <param name="query"></param>
        /// <param name="ctx"></param>
        /// <returns></returns>
        private static object CreateQuery(Type modelType, string query, ObjectContext ctx)
        {
            var entitySetFullName = GetEntitySetFullName(modelType, ctx);

            var esqlWithParams = CreateESQL(modelType, query, entitySetFullName);

            var queryObject = ctx.GetType().GetMethod("CreateQuery")
                    .MakeGenericMethod(new Type[] { modelType })
                    .Invoke(ctx, esqlWithParams);

            return queryObject;
        }


        /// <summary>
        /// Cria a ESQL dinamicamente usando os campos marcados como 
        /// </summary>
        /// <param name="modelType"></param>
        /// <param name="query"></param>
        /// <param name="entitySetFullName"></param>
        /// <returns></returns>
        private static object[] CreateESQL(Type modelType, string query, string entitySetFullName)
        {
            var esql = @"SELECT VALUE val FROM " + entitySetFullName + " AS val ";

            var columns = modelType.GetProperties().Where(p => p.CustomAttributes.Count(c => c.AttributeType.Name.Equals("LookupListAttribute")) > 0);

            var i = 0;
            foreach (var c in columns)
            {
                if (c.PropertyType == typeof(string))
                {
                    esql += i == 0 ? " WHERE val." + c.Name + " like @value" : " OR val." + c.Name + " like @value ";
                    i++;
                }
                else if(c.PropertyType == typeof(int) || c.PropertyType == typeof(double) || c.PropertyType == typeof(float))
                {
                    i++;
                    esql += i == 0 ? " WHERE val." + c.Name + " = @value" : " OR val." + c.Name + " = @value ";
                }                
            }

            ObjectParameter param;
            
            param = new ObjectParameter("value", typeof(string));
            param.Value = String.Format("%{0}%",query);

            var esqlWithParams = new object[] { esql, new ObjectParameter[] { param } };
            return esqlWithParams;
        }

        /// <summary>
        /// Busca o nome da Entidade
        /// </summary>
        /// <param name="modelType"></param>
        /// <param name="ctx"></param>
        /// <returns></returns>
        private static string GetEntitySetFullName(Type modelType, System.Data.Objects.ObjectContext ctx)
        {
            var container = ctx.MetadataWorkspace.GetEntityContainer(
               ctx.DefaultContainerName, DataSpace.CSpace);

            var entityName = (from meta in container.BaseEntitySets
                              where meta.ElementType.Name == modelType.Name
                              select meta.Name).FirstOrDefault();

            var entitySetFullName = ctx.DefaultContainerName + "." + entityName;
            return entitySetFullName;
        }

        /// <summary>
        /// Retorna o método genérico ToList<T> usando o <paramref name="entityType"/> como T.
        /// </summary>
        /// <param name="entityType">Tipo da entidade</param>
        /// <param name="objectQuery">Query para invocar o ToList</param>
        /// <returns></returns>
        public static object InvokeToList(Type entityType, object objectQuery)
        {
            var flags = BindingFlags.Static | BindingFlags.Public;

            var method = typeof(Enumerable).GetMethods(flags).Where(c => c.Name.Contains("ToArray")).FirstOrDefault();

            return method.MakeGenericMethod(new Type[] { entityType })
                .Invoke(objectQuery, new object[] { objectQuery });
        }

    }
}
